﻿using Windows.UI.Xaml;
using Windows.UI.Xaml.Data;

namespace Uhimania.Libraries.Windows.Interactivity
{
    #region AttachedObjectBase
    /// <summary>
    /// 別のオブジェクトにアタッチ可能なオブジェクトの基底クラス。
    /// </summary>
    public abstract class AttachedObjectBase : FrameworkElement, IAttachedObject
    {
        #region AssociatedObject

        /// <summary>
        /// インスタンスがアタッチされているオブジェクト。
        /// </summary>
        private DependencyObject associatedObject;

        /// <summary>
        /// インスタンスがアタッチされているオブジェクトを取得する。
        /// </summary>
        public DependencyObject AssociatedObject
        {
            get { return this.associatedObject; }
            protected set
            {
                if (object.Equals(this.associatedObject, value))
                    return;

                var oldValue = this.associatedObject;

                this.associatedObject = value;

                this.DetachEvents(oldValue);
                this.AttachEvents(value);

                this.OnAssociatedObjectChanged(oldValue, value);
            }
        }

        /// <summary>
        /// このインスタンスがアタッチされているオブジェクトがレイアウトされたとき
        /// このインスタンスの DataContext プロパティをバインドする。
        /// </summary>
        /// <param name="sender">イベントソース。</param>
        /// <param name="e">イベントデータ。</param>
        private void OnLayoutUpdated(object sender, object e)
        {
            var element = this.associatedObject as FrameworkElement;
            if (element != null)
            {
                element.LayoutUpdated -= this.OnLayoutUpdated;
                this.SetBinding(DataContextProperty, new Binding { Source = element, Path = new PropertyPath("DataContext"), Mode = BindingMode.OneWay });
            }
        }

        /// <summary>
        /// アタッチされたオブジェクトがアンロードされたとき
        /// <see cref="AssociatedObject"/> をクリアする。
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void OnUnloaded(object sender, RoutedEventArgs e)
        {
            this.AssociatedObject = null;
        }

        /// <summary>
        /// このオブジェクトが適切にレイアウトされるために必要なイベントをアタッチする。
        /// </summary>
        /// <param name="obj">アタッチされるオブジェクト。</param>
        private void AttachEvents(DependencyObject obj)
        {
            var element = obj as FrameworkElement;
            if (element != null)
            {
                element.LayoutUpdated += this.OnLayoutUpdated;
                element.Unloaded += this.OnUnloaded;
            }
        }

        /// <summary>
        /// オブジェクトがデタッチされるとき、イベントを解除する。
        /// </summary>
        /// <param name="obj">デタッチされるオブジェクト。</param>
        private void DetachEvents(DependencyObject obj)
        {
            var element = obj as FrameworkElement;
            if (element != null)
            {
                element.LayoutUpdated -= this.OnLayoutUpdated;
                element.Unloaded -= this.OnUnloaded;
            }

            this.ClearValue(DataContextProperty);
        }

        /// <summary>
        /// <see cref="AssociatedObject"/> が変更されたときに呼び出される。
        /// </summary>
        /// <param name="oldValue">変更前の値。</param>
        /// <param name="newValue">変更後の値。</param>
        protected virtual void OnAssociatedObjectChanged(DependencyObject oldValue, DependencyObject newValue)
        {
        }

        #endregion

        #region Attach

        /// <summary>
        /// 指定されたオブジェクトにアタッチする。
        /// </summary>
        /// <param name="associatedObject">アタッチするオブジェクト。</param>
        public void Attach(DependencyObject associatedObject)
        {
            this.AssociatedObject = associatedObject;
            this.OnAttached();
        }

        /// <summary>
        /// このインスタンスが <see cref="AssociatedObject"/> にアタッチされた後で呼び出される。
        /// </summary>
        protected virtual void OnAttached()
        {
        }

        #endregion

        #region Detach

        /// <summary>
        /// このインスタンスを関連オブジェクトからデタッチする。
        /// </summary>
        public void Detach()
        {
            this.OnDetaching();
            this.AssociatedObject = null;
        }

        /// <summary>
        /// このインスタンスが AssociatedObject からデタッチされるとき、その前に呼び出される。
        /// </summary>
        protected virtual void OnDetaching()
        {
        }

        #endregion
    }
    #endregion
}
